//haven't tested this class yet

package edu.arizona.sista.learn.evaluation;

import java.util.ArrayList;
import java.util.List;

import edu.arizona.sista.learn.activity.model.Predicate;
import edu.arizona.sista.learn.activity.model.PredicateInterval;
import edu.arizona.sista.learn.activity.model.args.*;

import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;

public class OracleBinder {
	protected Set<PredArg> foundArgs;
	protected Set<PredArg> goldArgs;
	protected List<PredicateInterval> foundBindings;
	protected List<PredicateInterval> goldBindings;
	
	public OracleBinder(List<PredicateInterval> fBindings, List<PredicateInterval> gBindings) {
		foundBindings = fBindings;
		goldBindings = gBindings;
	}
	
	public void findArgs() {
		for (PredicateInterval curInterval : foundBindings) {
			Predicate tempPred = curInterval.getPredicate();
			PredArg[] tempArgs = tempPred.getArgs();
			for (PredArg tempArg : tempArgs) {
				if(!(foundArgs.contains(tempArg)))
					foundArgs.add(tempArg);
			}
		}
		for (PredicateInterval curInterval : goldBindings) {
			Predicate tempPred = curInterval.getPredicate();
			PredArg[] tempArgs = tempPred.getArgs();
			for (PredArg tempArg : tempArgs) {
				if(!(goldArgs.contains(tempArg)))
					goldArgs.add(tempArg);
			}
		}
	}
	
	public void tryBindings() {
		PredArg[] foundArgArray = foundArgs.toArray(foundBindings.get(0).getPredicate().getArgs());
		PredArg[] goldArgArray = goldArgs.toArray(foundBindings.get(0).getPredicate().getArgs());
		int numFoundArgs = foundArgArray.length;
		int numGoldArgs = goldArgArray.length;
		List<PredicateInterval> curBindings = new ArrayList<PredicateInterval>();
		
		
		//number of ways to map foundargs to goldargs is goldargs^foundargs since we're trying every possible mapping
		//and checking for conflicts later
		/*int numPerm = (int) Math.pow(numFoundArgs,numGoldArgs);
		for(int i = 0; i < numPerm; i++) {
			HashMap<PredArg,PredArg> bindingsMap = new HashMap<PredArg,PredArg>();
			//remArgs is initially the index of the permutation we're generating, divide it at each step
			//since we've matched one step of the permutation
			for(int j = 0, remArgs = i; j < numGoldArgs; j++, remArgs /= numFoundArgs) {
				//tells us which mapping to use for this step
				int mapIndex = remArgs % numGoldArgs;
				//can't map, for example, object to agent
				if(foundArgArray[mapIndex].lessSpecificThan(goldArgArray[j])) {
					bindingsMap = null;
					break;
				}
				bindingsMap.put(foundArgArray[mapIndex], goldArgArray[j]);
			}
			if(bindingsMap != null)
				curBindings = applyBindings(bindingsMap);
			if(curBindings != null) {
				//evaluate bindings here
			}
		}*/
		
		Boolean foundMin = (numFoundArgs < numGoldArgs)?true:false;
		int maxFact = 1;
		for(int i = foundMin?numGoldArgs:numFoundArgs; i > 0; i--)
			maxFact *= i;
		int minFact = 1;
		for(int i = foundMin?numGoldArgs-numFoundArgs:numFoundArgs-numGoldArgs; i > 0; i--)
			minFact *= i;
		int numPerms = maxFact/minFact;
		if(foundMin) //more gold args than found args, so iterate over found args and map onto gold
			for(int i = 0; i < numPerms; i++) {
				HashMap<PredArg,PredArg> bindingsMap = new HashMap<PredArg,PredArg>();
				int cycleAfter = numPerms;
				int remOffset = i;
				int allocated = 0;
				for(int j = numFoundArgs; j > 0; j--) {
					cycleAfter /= (numGoldArgs - (numFoundArgs - j));
					int curOffset = remOffset / cycleAfter;
					int mapIndex = 0;
					for(;; mapIndex++)
						if((allocated ^ (1 << (mapIndex % numGoldArgs))) != 0)
							if(curOffset-- < 0)
								break;
					if(foundArgArray[numFoundArgs - j].lessSpecificThan(goldArgArray[mapIndex])) {
						//invalid binding if maps, say, object to agent
						bindingsMap = null;
						i += cycleAfter;
						//can skip every assignment that maps this found object to this gold object
						break;
					}
					bindingsMap.put(foundArgArray[numFoundArgs - j], goldArgArray[mapIndex]);
					remOffset %= cycleAfter; 
				}
				if(bindingsMap != null) {
					//try metrics here
				}
			}
		else { //iterate over gold args and map found args to them
			for(int i = 0; i < numPerms; i++) {
				HashMap<PredArg,PredArg> bindingsMap = new HashMap<PredArg,PredArg>();
				int cycleAfter = numPerms;
				int remOffset = i;
				int allocated = 0;
				for(int j = numGoldArgs; j > 0; j--) {
					cycleAfter /= (numFoundArgs - (numGoldArgs - j));
					int curOffset = remOffset / cycleAfter;
					int mapIndex = 0;
					for(;; mapIndex++)
						if((allocated ^ (1 << (mapIndex % numFoundArgs))) != 0)
							if(curOffset-- < 0)
								break;
					if(foundArgArray[mapIndex].lessSpecificThan(goldArgArray[numGoldArgs - j])) {
						//invalid binding if maps, say, object to agent
						bindingsMap = null;
						i += cycleAfter;
						//can skip every assignment that maps this found object to this gold object
						break;
					}
					bindingsMap.put(foundArgArray[mapIndex], goldArgArray[numGoldArgs - j]);
					remOffset %= cycleAfter; 
				}
				if(bindingsMap != null) {
					//try metrics here
				}
			}
		}
	}
	
	public List<PredicateInterval> applyBindings(HashMap<PredArg,PredArg> bindingsMap) {
		List<PredicateInterval> newBindings = new ArrayList<PredicateInterval>();
		for(PredicateInterval curInterval : foundBindings) {
			PredicateInterval tempInterval = new PredicateInterval(curInterval);
			List<PredArg> argList = new ArrayList<PredArg>();
			Set<PredArg> usedArgs = new HashSet<PredArg>();
			for(PredArg tempArg : (curInterval.getPredicate().getArgs())) {
				//ensure that no predicate has duplicate arguments - might not want this?
				if(!(usedArgs.add(tempArg)))
					return null;
				PredArg remappedArg = tempArg;
				remappedArg.setVariableName(bindingsMap.get(tempArg).getVariableName());
				argList.add(remappedArg);
			}
			Predicate tempPred = new Predicate(curInterval.getName(), argList.toArray(curInterval.getPredicate().getArgs()));
			tempInterval.setPredicate(tempPred);
			newBindings.add(tempInterval);
		}
		return newBindings;
	}
}
